from django.shortcuts import HttpResponseRedirect, render_to_response
from django.template import RequestContext
from django.contrib import messages

from models import *
from apps.course.models import Course
from apps.management import Management
from apps.participants.models import CourseParticipant

import logging
import re

logger = logging.getLogger('pet.apps.gradebook.functions')
def CopyGroups(TheAssignment,AssignmentToCopy,GroupedUsers,UngroupedUsers):
    #Delete the groups in TheAssignment
    for group in TheAssignment.groups.all():
        group.delete()
    #Create the groups from AssignmentToCopy
    for group in AssignmentToCopy.groups.all():
        NewGroup = GradingGroup(group_name=group.group_name,content_object=TheAssignment)
        NewGroup.save()
        for user in group.users.all():
            if user in GroupedUsers or user in UngroupedUsers: NewGroup.users.add(user)

def GenerateGradingSectionGradeForms(graded, userlist, postinfo=None):
    GradingForms = {}
    for user in userlist:
        GradingForms[user] = {}
        if graded:
            for section in graded.sections.all():
                SectionGrade = GradingSectionGrade.objects.filter(section=section,user=user)
                if SectionGrade: SectionGrade = SectionGrade[0]
                else:
                    SectionGrade = GradingSectionGrade(section=section,user=user)
                    SectionGrade.save()
                GradingForms[user][section] = GradingSectionGradeForm(postinfo,instance=SectionGrade,prefix="section%s%s"%(section.section_number,user.username))
    return GradingForms
def GenerateGradingSectionForms(graded, postinfo=None):
    SectionForms = []
    if graded:
        for section in graded.sections.order_by('section_number'):
            SectionForm = GradingSectionForm(postinfo,instance=section,prefix="section%s"%(section.section_number))
            SectionForms.append(SectionForm)
    return SectionForms
def GenerateAssignmentWeightFormset(course,category,postinfo=None):
    AWForms = []
    for interface in Gradeables.get_interfaces():
        Assignments = interface.find(course=course,category_slug=category)
        if Assignments:
            for assignment in Assignments:
                AWForm = GradedWeightForm(postinfo,instance=assignment,prefix="assignment%s%s"%(assignment.category_slug,assignment.title_slug))
                AWForms.append(AWForm);
    if not AWForms:
            logger.http404("No gradeables found with category %s",category)
            raise Http404
    return AWForms

def GenerateGroupFormset(course_id,assignment,postinfo=None):
    CPs = CourseParticipant.find(course=course_id,status="STUDENT",keep_queryset=True)
    CPCount = CPs.count()
    GroupCount = 0
    Forms = []
    Ungrouped = []
    Multigrouped = {}
    GroupUsers = {}
    #Find all groups already created for this assignment
    Groups = assignment.groups.all()
    if Groups:
        for group in Groups:
            Forms.append(GradingGroupEditForm(course_id,postinfo,instance=group,prefix=slugify(group.group_name)))
            for user in group.users.all():
                if not user in GroupUsers: GroupUsers[user] = []
                GroupUsers[user].append(group)
                GroupCount += 1
    
    #Add ability to make new group
    Forms.append(GradingGroupEditForm(course_id,postinfo))
    
    #Check for non-group membership
    for cp in CPs:
        if not cp.user in GroupUsers:
            Ungrouped.append(cp.user)
            
    #Check for multi-group membership
    for user in GroupUsers:
        if len(GroupUsers[user]) > 1:
            Multigrouped[user] = GroupUsers[user]
            
    return Forms,GroupUsers,Ungrouped,Multigrouped

def RetrieveGrades(TheCourse, filter_category=None, filter_assignment=None):
    CourseUsers = CourseParticipant.find(TheCourse.course_id,status="STUDENT",keep_queryset=True)
    if not CourseUsers: return None, None, None
    Grades = {}
    Categories = {}
    Metrics = {'info':{'grade_list':[]}}
    NewCategory = False
    CategoryWeightSum = 0
    for user in CourseUsers:
        Grades[user.user] = {};
    # Categories[category][assignment][section] = {'total_points', 'title'}
    # Categories[category][assignment]['info'] = {'total_points', 'weight', 'title', 'slug','object','edit_url'}
    # Categories[category]['info'] = {'total_points', 'title', 'slug', 'weight', 'grading', 'option','option_argument','number'}
    # Grades[user][category][assignment][section] = {'points','notes'}
    # Grades[user][category][assignment]['info'] = {'points','percent','dropped'}
    # Grades[user][category]['info'] = {'points','average','weighted_average'}
    # Grades[user]['Total'] = overall grade for user
    # Metrics[category][assignment][section] = {'max', 'min', 'average', 'median', 'grade_list'}
    # Metrics[category][assignment]['info'] = {'max', 'min', 'average', 'median', 'grade_list'}
    # Metrics[category]['info']['points'] = {'max', 'min', 'average', 'median', 'grade_list'}
    # Metrics[category]['info']['average'] = {'max', 'min', 'average', 'median', 'grade_list'}
    # Metrics[category]['info']['weighted_average'] = {'max', 'min', 'average', 'median', 'grade_list'}
    # Metrics['info'] = {'max', 'min', 'average', 'median', 'grade_list'}
    temp_categories = {}
    for interface in Gradeables.get_interfaces():
        temp_categories[interface] = interface.get_grades(course=TheCourse.course_id, classname=interface,
            category_slug=filter_category if filter_category else None, title_slug=filter_assignment if filter_assignment else None)
    
    #Data Structure Initialization
    # This is peppered with a lot of dict content checking before doing initialization. It was causing
    #   errors so I put it in in every case even if chances were remote (or zero) that data could be
    #   overwritten, just to be safe
    for interface in temp_categories:
        if not temp_categories[interface]: continue
        for category, assignments in temp_categories[interface].items():
            #Check that we have a GradingCategory object for this category, if not, make one
            grading_category = GradingCategory.find(course=TheCourse.course_id,name=category)
            if not grading_category:
                grading_category = GradingCategory(name=category,course=TheCourse.course_id)
                grading_category.save()
                NewCategory = True
            #Add this category's info to our Categories dict
            if not category in Categories:
                Categories[category] = {'info':{'title':grading_category.name,
                                    'slug':slugify(grading_category.name),
                                    'weight':grading_category.weight,
                                    'grading':grading_category.grading,
                                    'option':grading_category.option,
                                    'option_argument':grading_category.option_argument,
                                    'number':grading_category.number,
                                    'total_points':0}}
            #Initialize our Metrics dict for this category
            if not category in Metrics: Metrics[category] = {'info':{'points':{'grade_list':[]},'average':{'grade_list':[]},'weighted_average':{'grade_list':[]}}}
            #Initialize our Grades for this category for each user
            for user in Grades:
                if not category in Grades[user]: Grades[user][category] = {'info':{'total_points':0,'points':0,'average':0,'weighted_average':0,'assignment_count':0,'assignment_weight_sum':0}}
            #initialize our data structures for each assignment
            for assignment in assignments:
                if not assignment in Categories[category]:
                    Categories[category][assignment] = {'info':{'title':assignment.title,
                                                            'slug':assignment.title_slug,
                                                            'weight':assignment.weight,
                                                            'total_points':assignment.total_points,
                                                            'object':assignment,
                                                            'edit_url':assignment.get_edit_url(TheCourse.course_id)}}
                #Add the total points for the category
                Categories[category]['info']['total_points'] += assignment.total_points
                if not assignment in Metrics[category]: Metrics[category][assignment] = {'info':{'grade_list':[]}}
                #Go through each section and initialize the data structures for it
                for user in Grades:
                        if not assignment in Grades[user][category]: Grades[user][category][assignment] = {'info':{'points':0,'percent':0,'dropped':False}}
                for section in assignment.sections.all():
                    if not section in Categories[category][assignment]:
                        Categories[category][assignment][section] = {'total_points':section.total_points,
                                                                'title':section.title if section.title else "Section %s" % section.section_number}
                    if not section in Metrics[category][assignment]: Metrics[category][assignment][section] = {'grade_list':[]}
                    for user in Grades:
                        if not section in Grades[user][category][assignment]: Grades[user][category][assignment][section] = {'points':0,'notes':""}
    
    #Now go back through the data and actually populate everything
    for interface in temp_categories:
        if not temp_categories[interface]: continue
        for category, assignments in temp_categories[interface].items():
            for user in Grades:
                for assignment in assignments:
                    for section in assignment.sections.all():
                        grade = section.grades.filter(user=user)
                        if grade:
                            Grades[user][category][assignment][section] = grade[0]
                            #Grades[user][category][assignment][section]['points'] += grade.points
                            #Grades[user][category][assignment][section]['notes'] += grade.notes if grade.notes else ''
                            Grades[user][category][assignment]['info']['points'] += grade[0].points
                            Grades[user][category][assignment]['info']['percent'] += 100.0*grade[0].points/(assignment.total_points or 1.0)
                            Metrics[category][assignment][section]['grade_list'].append(Grades[user][category][assignment][section].points)
                    Metrics[category][assignment]['info']['grade_list'].append(Grades[user][category][assignment]['info']['percent'])
    
    for user, categories in Grades.items():
        for category,assignments in categories.items():
            #Figure out which assignments get dropped
            AssignmentList = []
            for assignment in assignments:
                if assignment == 'info': continue
                AssignmentList.append((Grades[user][category][assignment]['info']['percent'],assignment))
            AssignmentList.sort()
            DroppedAssignments = []
            if Categories[category]['info']['option'] == 'LOWEST' and len(AssignmentList) > Categories[category]['info']['option_argument']:
                DroppedAssignments = AssignmentList[:Categories[category]['info']['option_argument']]
            elif Categories[category]['info']['option'] == 'HIGHEST' and len(AssignmentList) > Categories[category]['info']['option_argument']:
                DroppedAssignments = AssignmentList[:-Categories[category]['info']['option_argument']]
            for grade,drop in DroppedAssignments:
                Grades[user][category][drop]['info']['dropped'] = True
                
            #Now calcuate grades and remaining metrics for non-dropped assignments
            #for each user we need to figure out -
            #   - the total # of an assignments in each category
            #   - the total weight of assignments in each category
            #   - the total points earned on assignments in each category
            #   - the total weighted points earned on assignments in each category
            assignment_weight_sum = 0
            total_category_points = 0
            points_sum = 0
            percent_sum = 0
            weighted_percent_sum = 0
            for assignment in assignments:
                if assignment == 'info': continue
                if Grades[user][category][assignment]['info']['dropped'] == False:
                    Grades[user][category]['info']['assignment_count'] += 1
                    Grades[user][category]['info']['assignment_weight_sum'] += assignment.weight
                    Grades[user][category]['info']['total_points'] += assignment.total_points
                    Grades[user][category]['info']['points'] += Grades[user][category][assignment]['info']['points']
                    Grades[user][category]['info']['average'] += 100.0*Grades[user][category][assignment]['info']['points']/(assignment.total_points or 1.0)
                    Grades[user][category]['info']['weighted_average'] += 100.0*assignment.weight*Grades[user][category][assignment]['info']['points']/(assignment.total_points or 1.0)
        
    #Check that we have generated grade/category info
    if not Categories or not Grades:
        return None, None, None

    #Go back through our totals now and reduce the running tallies down to final scores per category
    for user, categories in Grades.items():
        for category in Categories:
            #Points = 100.0*total points/category points, Percent = total percents/# assignments, Weighted percent = total weighted percents/total weights
            Grades[user][category]['info']['points'] /= .01*(Grades[user][category]['info']['total_points'] or 1.0)
            Grades[user][category]['info']['average'] /= (Grades[user][category]['info']['assignment_count'] or 1.0)
            Grades[user][category]['info']['weighted_average'] /= (Grades[user][category]['info']['assignment_weight_sum'] or 1.0)
            #Add these grades to the grade lists we make for calculating metrics
            Metrics[category]['info']['points']['grade_list'].append(Grades[user][category]['info']['points'])
            Metrics[category]['info']['average']['grade_list'].append(Grades[user][category]['info']['average'])
            Metrics[category]['info']['weighted_average']['grade_list'].append(Grades[user][category]['info']['weighted_average'])
            
    #Get the sum of category weights
    CategoryWeightSum = 0
    for category in Categories:
        CategoryWeightSum += Categories[category]['info']['weight']
    
    #Then figure out user final grades based on category grades
    for user,categories in Grades.items():
        Grades[user]['Total'] = 0
        for category in Categories:
            if Categories[category]['info']['grading'] == "SUM":
                grade = Grades[user][category]['info']['points']
            elif Categories[category]['info']['grading'] == "AVERAGE":
                grade = Grades[user][category]['info']['average']
            elif Categories[category]['info']['grading'] == "WEIGHTED_AVERAGE":
                grade = Grades[user][category]['info']['weighted_average']
            Grades[user]['Total'] += grade*(Categories[category]['info']['weight'])/(CategoryWeightSum or 1.0)
        Metrics['info']['grade_list'].append(Grades[user]['Total'])
        
    #Reorder categories if we made new ones (to keep numbering consistent)
    if NewCategory:
        index = 1
        GradingCategories = GradingCategory.find(course=TheCourse.course_id,keep_queryset=True)
        if GradingCategories:
            for category in GradingCategories.order_by('number'):
                category.number = index
                category.save()
                if category.name in Categories: Categories[category.name]['info']['number'] = index
                index += 1
                
    #Generate Metrics
    for category, assignments in Metrics.items():
        if category == 'info':
            grade_count = len(Metrics['info']['grade_list'])
            Metrics['info']['min'] = min(Metrics['info']['grade_list'])
            Metrics['info']['max'] = max(Metrics['info']['grade_list'])
            Metrics['info']['average'] = float(sum(Metrics['info']['grade_list']))/grade_count
            Metrics['info']['median'] = sorted(Metrics['info']['grade_list'])[grade_count/2]
            continue
        for assignment, sections in assignments.items():
            if assignment == 'info':
                grade_count = len(Metrics[category]['info']['points']['grade_list'])
                Metrics[category]['info']['points']['min'] = min(Metrics[category]['info']['points']['grade_list'])
                Metrics[category]['info']['points']['max'] = max(Metrics[category]['info']['points']['grade_list'])
                Metrics[category]['info']['points']['average'] = float(sum(Metrics[category]['info']['points']['grade_list']))/grade_count
                Metrics[category]['info']['points']['median'] = sorted(Metrics[category]['info']['points']['grade_list'])[grade_count/2]
                grade_count = len(Metrics[category]['info']['average']['grade_list'])
                Metrics[category]['info']['average']['min'] = min(Metrics[category]['info']['average']['grade_list'])
                Metrics[category]['info']['average']['max'] = max(Metrics[category]['info']['average']['grade_list'])
                Metrics[category]['info']['average']['average'] = float(sum(Metrics[category]['info']['average']['grade_list']))/grade_count
                Metrics[category]['info']['average']['median'] = sorted(Metrics[category]['info']['average']['grade_list'])[grade_count/2]
                grade_count = len(Metrics[category]['info']['points']['grade_list'])
                Metrics[category]['info']['weighted_average']['min'] = min(Metrics[category]['info']['weighted_average']['grade_list'])
                Metrics[category]['info']['weighted_average']['max'] = max(Metrics[category]['info']['weighted_average']['grade_list'])
                Metrics[category]['info']['weighted_average']['average'] = float(sum(Metrics[category]['info']['weighted_average']['grade_list']))/grade_count
                Metrics[category]['info']['weighted_average']['median'] = sorted(Metrics[category]['info']['weighted_average']['grade_list'])[grade_count/2]
                continue
            
            for section in sections:
                if section == 'info':
                    grade_count = len(Metrics[category][assignment]['info']['grade_list'])
                    Metrics[category][assignment]['info']['min'] = min(Metrics[category][assignment]['info']['grade_list'])
                    Metrics[category][assignment]['info']['max'] = max(Metrics[category][assignment]['info']['grade_list'])
                    Metrics[category][assignment]['info']['average'] = float(sum(Metrics[category][assignment]['info']['grade_list']))/grade_count
                    Metrics[category][assignment]['info']['median'] = sorted(Metrics[category][assignment]['info']['grade_list'])[grade_count/2]
                    continue
                
                grade_count = len(Metrics[category][assignment][section]['grade_list'])
                Metrics[category][assignment][section]['min'] = min(Metrics[category][assignment][section]['grade_list']) if Metrics[category][assignment][section]['grade_list'] else 0.0
                Metrics[category][assignment][section]['max'] = max(Metrics[category][assignment][section]['grade_list']) if Metrics[category][assignment][section]['grade_list'] else 0.0
                Metrics[category][assignment][section]['average'] = float(sum(Metrics[category][assignment][section]['grade_list']))/grade_count if Metrics[category][assignment][section]['grade_list'] else 0.0
                Metrics[category][assignment][section]['median'] = sorted(Metrics[category][assignment][section]['grade_list'])[grade_count/2] if Metrics[category][assignment][section]['grade_list'] else 0.0
                    
    return Categories, Grades, Metrics